#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>

#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static DECLARE_WAIT_QUEUE_HEAD(buttons_wait);
static volatile int ev_press = 0;

static struct cdev buttons_cdev;
static struct class *buttons_cls;
static struct class_device *buttons_class_dev;

static int major;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

struct pin_desc {
	unsigned int pin;
	unsigned int key_val;
};

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;

struct pin_desc pins_desc[4] = {
	{S3C2410_GPF0, 0x01},
	{S3C2410_GPF2, 0x02},
	{S3C2410_GPG3, 0x03},
	{S3C2410_GPG11, 0x04},
};

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

	ev_press = 1;
	wake_up_interruptible(&buttons_wait);

	return IRQ_RETVAL(IRQ_HANDLED);
}

static int buttons_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0,2为中断引脚 */
	*gpfcon &= ~((3<<(0*2)) | (3<<(2*2)));
	*gpfcon |= ((2<<(0*2)) | (2<<(2*2)));

	/* 配置GPG3,11为中断引脚 */	
	*gpgcon &= ~((3<<(3*2)) | (3<<(11*2)));
	*gpgcon |= ((2<<(3*2)) | (2<<(11*2)));

	if (request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]))
		return EAGAIN;
	if (request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]))
		return EAGAIN;
	if (request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]))
		return EAGAIN;
	if (request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]))
		return EAGAIN;

	return 0;
}

ssize_t buttons_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	if (size != 1)
			return -EINVAL;

	wait_event_interruptible(buttons_wait, ev_press);

	if (copy_to_user(buf, &key_val, 1))
		return -EFAULT;

	ev_press = 0;

	return 0;
}

static int buttons_close(struct inode *inode, struct file *file)
{
	free_irq(IRQ_EINT0, &pins_desc[0]);
	free_irq(IRQ_EINT2, &pins_desc[1]);
	free_irq(IRQ_EINT11, &pins_desc[2]);
	free_irq(IRQ_EINT19, &pins_desc[3]);

	return 0;
}

static struct file_operations buttons_fops = {
	.owner	= THIS_MODULE,
	.open	= buttons_open,
	.read   = buttons_read,
	.release = buttons_close, 
};

static int __init buttons_drv_init(void)
{
	int ret;
	dev_t devid;

	ret = alloc_chrdev_region(&devid, 0, 1, "buttons");
	if (ret != 0)
	{
		printk("alloc chrdev failed!");
	}
	major = MAJOR(devid);

	cdev_init(&buttons_cdev, &buttons_fops);
	cdev_add(&buttons_cdev, MKDEV(major, 0), 1);

	buttons_cls = class_create(THIS_MODULE, "buttons_drv");
	buttons_class_dev = class_device_create(buttons_cls, NULL, MKDEV(major, 0), NULL, "buttons");

	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 4);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 4);
	gpgdat = gpgcon + 1;

	return 0;
}

static void __exit buttons_drv_exit(void)
{
	class_device_destroy(buttons_cls, MKDEV(major, 0));
	class_destroy(buttons_cls);
	cdev_del(&buttons_cdev);
	unregister_chrdev_region(MKDEV(major, 0), 1);
	iounmap(gpfcon);
	iounmap(gpgcon);
}

module_init(buttons_drv_init);
module_exit(buttons_drv_exit);

MODULE_LICENSE("GPL v2");

